package ar.com.fiuba.RulesMgr;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import ar.com.fiuba.RulesMgr.connectors.Connector;
import ar.com.fiuba.RulesMgr.exceptions.DuplicateRuleException;
import ar.com.fiuba.RulesMgr.exceptions.InvalidRuleException;
import ar.com.fiuba.RulesMgr.exceptions.RuleNotFoundException;
import ar.com.fiuba.RulesMgr.rules.CompositeRule;
import ar.com.fiuba.RulesMgr.rules.GenericRule;
import ar.com.fiuba.RulesMgr.validationModes.OrderedValidationMode;
import ar.com.fiuba.RulesMgr.validationModes.UnorderedValidationMode;
import ar.com.fiuba.RulesMgr.validationModes.ValidationMode;

/**
 * Interface with client application for rules validation.
 * 
 */
public class RulesManager {
	
	private Logger log = Logger.getLogger(RulesManager.class); 

	private final Map<String, GenericRule> rules = new HashMap<String, GenericRule>();

	private ValidationMode validationMode = new UnorderedValidationMode();

	public RulesManager() {
	}

	/**
	 * Method to add a rule to the set of rules the manager holds.
	 * 
	 * @param rule
	 *            the rule to be added.
	 * @return the instance of the rule added.
	 * @throws DuplicateRuleException
	 *             if rule with the same name already exists.
	 */
	public GenericRule addRule(GenericRule rule) throws DuplicateRuleException {
		
		log.info("Trying to add rule with name " + rule.getName() + "to the repository...");
		
		if (rules.containsKey(rule.getName())){
			log.error("A rule with a name '" + rule.getName() + "' already exists in the repository");
			throw new DuplicateRuleException("The rule '" + rule.getName() + "' already exists.");
		}
		rules.put(rule.getName(), rule);
		
		log.info("Rule addedd successfully to the repository");
		return rule;
	}

	/**
	 * Method to combine two rules into a new one. This one will be added to the
	 * manager.
	 * 
	 * @param ruleName
	 *            the name of the new rule.
	 * @param firstRule
	 *            the first rule of the composition.
	 * @param secondRule
	 *            the second rule of the composition.
	 * @param connector
	 *            the connector to connect the two rules.
	 * @return the new rule.
	 * @throws DuplicateRuleException
	 *             if rule with the name provided already exists.
	 * @throws InvalidRuleException
	 *             if invalid parameters are provided, such as null values.
	 */
	public GenericRule combineRules(String ruleName, GenericRule firstRule, GenericRule secondRule, Connector connector)
			throws DuplicateRuleException, InvalidRuleException {
		
		log.info("Trying to relate rule " + firstRule.getName() + "and rule " + secondRule.getName());
		
		if (rules.containsKey(ruleName)){
			log.error("A rule with a name '" + ruleName + "' already exists in the repository");
			throw new DuplicateRuleException("The rule '" + ruleName + "' already exists.");
		}

		GenericRule genericRule = new CompositeRule(ruleName, firstRule, secondRule, connector);
		rules.put(ruleName, genericRule);
		
		log.info("Rules linked successfully");

		return genericRule;
	}

	/**
	 * Method to validate a specific rule over a set of objects.
	 * 
	 * @param ruleName
	 *            the name of the rule to be validated.
	 * @param objects
	 *            the objects to be validated.
	 * @return true if the rule is satisfied, otherwise false.
	 * @throws RuleNotFoundException
	 *             if rule hasn't been added to the manager.
	 * @throws InvalidRuleException
	 *             if the Rule hasn'got the enough info to be evaluated
	 */
	public boolean validate(String ruleName, List<Object> objects) throws RuleNotFoundException {
		
		log.info("Starting to validate the rule " + ruleName);
		
		GenericRule ruleToValidate = rules.get(ruleName);
		if (ruleToValidate == null){
			log.error("The rule '" + ruleName +"' does not exist in the repository");
			throw new RuleNotFoundException("Rule '" + ruleName + "' not found.");
		}
		return ruleToValidate.validateWithMode(validationMode, objects).booleanValue();
	}

	/**
	 * Method to set a parallel mode to validate rules.
	 */
	public void setUnorderedValidationMode() {
		validationMode = new UnorderedValidationMode();
	}

	/**
	 * Method to set a serial mode to validate rules.
	 */
	public void setOrderedValidationMode() {
		validationMode = new OrderedValidationMode();
	}

}
